nice_things/test/test.macro.sh
The macros in this file implement a minimal unit testing and benchmarking framework.
This macro file is in a directory which is not automatically imported by nice_build. When building your tests, you must manually include this file in the build using the -m/--macro option, like in the following example, which builds all test files under the ./src/test/ directory:
for src_file in $(glob ./src/test/**/[!._~]*.sh); do
./nice_build.sh -m nice_things/test/test.macro.sh -o "./build/${src_file#./src/}" "$src_file"
done
A typical test file starts by calling the test_init macro and importing the modules to be tested. Then the fail and or_fail macros are used to assert the result of each operation on the imported functions. At the end, depending on whether the test is being run in lenient_mode or in strict_mode, it is good practice to assert that those settings have not been accidentally changed by the tested functions using the is_lenient or the is_strict macro. Lastly, benchmarks can be added using the bench and end_bench pair of macros.
Here is a full example of test file using these macros:
#!/bin/sh
#{{{ strict_mode }}}
#{{{
test_init
import "{ hash_8bit }" from ./src/hash_8bit.sh
#}}}
expect=127
hash_8bit result 'known data' #{{{ or_fail }}}
[ "$expect" = "$result" ] #{{{ or_fail }}}
hash_8bit result 'random data' #{{{ or_fail }}}
case "$result" in "" | *[!0-9]* | 0[0-9]*)
#{{{ fail }}}
;;
esac
[ 0 -le "$result" ] && [ 255 -ge "$result" ] #{{{ or_fail }}}
#{{{ is_strict }}}
#{{{
bench 'hashing small data (36 bytes)' 1000
#}}}
c=0
while c=$((c + 1)) && [ "$c" -le 1000 ]; do
hash_8bit result 'abcdefghijklmnopqrstuvwxyz1234567890'
done
#{{{ end_bench }}}
bench
Since 0.3.0 · Source
Synopsis#{{{ bench <description> [<quantity>] }}}
Configuration
–
Description
This macro starts a benchmark block. Everything inside the benchmark block is executed in a sub-shell, and the total run-time is reported to stdout at the end.
The benchmark block must be ended with the accompanying end_bench macro. A test file can contain several benchmark blocks.
Benchmarks are only executed when the BENCH environment variable is set to a non-null value.
Options
–
Operands
<description>: Description of the benchmark.<quantity>: Integer number of times the benchmark loops.
Stdin
–
Stdout
The benchmark report is printed to stdout.
Stderr
–
Exit status
0: Successful completion.>0: Benchmark failed to complete.
Abort
Aborts the process with an error status code if the benchmark fails to complete.
Usage examples
#{{{
bench 'invocation cost of my_function' 1000
#}}}
c=0
while c=$((c + 1)) && [ "$c" -le 1000 ]; do
my_function
done
#{{{ end_bench }}}
end_bench
Since 0.3.0 · Source
Synopsis#{{{ end_bench }}}
Configuration
–
Description
This macro ends a benchmark block started by the accompanying bench macro.
Options
–
Operands
–
Stdin
–
Stdout
The benchmark report is printed to stdout.
Stderr
–
Exit status
0: Successful completion.>0: Benchmark failed to complete.
Abort
Aborts the process with an error status code if the benchmark fails to complete.
Usage examples
#{{{ end_bench }}}
fail
Since 0.3.0 · Source
Synopsis#{{{ fail }}}
Configuration
–
Description
Use this macro in a test failure condition.
Options
–
Operands
–
Stdin
–
Stdout
The failure report is printed to stdout.
Stderr
–
Exit status>0: Test failed.
Abort
Always aborts the process with an error status code.
Usage examples
case $- in *f*) ;; *)
#{{{ fail }}}
;;
esac
is_lenient
Since 0.3.0 · Source
Synopsis#{{{ is_lenient }}}
Configuration
–
Description
Assert that the shell options and value of the IFS variable conform to the lenient_mode defaults.
Some functions need to internally alter shell options or the value of the IFS shell-internal variable to do their work. Such functions must always revert these settings to their previous values before returning, otherwise they will create unintended side-effects that are very hard to debug. This and the accompanying is_strict macros exist to detect such side-effects and report it as a failure condition.
Options
–
Operands
–
Stdin
–
Stdout
The failure report is printed to stdout.
Stderr
–
Exit status
0: Successful completion.>0: Test failed.
Abort
Aborts the process with an error status code if the condition fails.
Usage examples
#{{{ is_lenient }}}
is_strict
Since 0.3.0 · Source
Synopsis#{{{ is_strict }}}
Configuration
–
Description
Assert that the shell options and value of the IFS variable conform to the strict_mode.
Some functions need to internally alter shell options or the value of the IFS shell-internal variable to do their work. Such functions must always revert these settings to their previous values before returning, otherwise they will create unintended side-effects that are very hard to debug. This and the accompanying is_lenient macros exist to detect such side-effects and report it as a failure condition.
Options
–
Operands
–
Stdin
–
Stdout
The failure report is printed to stdout.
Stderr
–
Exit status
0: Successful completion.>0: Test failed.
Abort
Aborts the process with an error status code if the condition fails.
Usage examples
#{{{ is_strict }}}
or_fail
Since 0.3.0 · Source
Synopsis#{{{ or_fail }}}
Configuration
–
Description
Use this macro to assert that the previous command succeeded.
This is a convenience macro. It prints the same code as the fail macro, preceded by an or-list operator (||).
Options
–
Operands
–
Stdin
–
Stdout
The failure report is printed to stdout.
Stderr
–
Exit status
0: Successful completion.>0: Test failed.
Abort
Aborts the process with the status code of the previous command if it returned a non-zero code.
Usage examples
expect='expected result'
result=$(some_function) #{{{ or_fail }}}
[ "$expect" = "$result" ] #{{{ or_fail }}}
test_init
Since 0.3.0 · Source
Synopsis#{{{ test_init }}}
Configuration
–
Description
Import dependencies required by the test macros. Should be used at the top of the module with the other imports.
Options
–
Operands
–
Stdin
–
Stdout
–
Stderr
–
Exit status0: Successful completion.
Abort
–
Usage examples
#!/bin/sh
#{{{
test_init
#}}}